package com.xlongwei.service;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.Map;

import org.noear.snack.ONode;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Param;
import org.noear.solon.core.handle.Context;
import org.noear.solon.data.cache.CacheService;
import org.noear.solon.validation.annotation.NotBlank;

import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.ChineseCaptcha;
import com.wf.captcha.ChineseGifCaptcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import com.xlongwei.util.ImageUtil;

import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.generator.impl.MultiImageCaptchaGenerator;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.impl.DefaultImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.impl.BasicCaptchaTrackValidator;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.ICaptcha;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.math.Calculator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@Mapping("service/checkcode")
public class Checkcode {
    ImageCaptchaGenerator imageCaptchaGenerator;
    ImageCaptchaValidator imageCaptchaValidator;
    @Inject
    CacheService cacheService;
    String cachePrefix = "sid:";
    int cacheSeconds = 120;
    int width = 130, height = 48;
    Field generatorField = null;

    @Init
    public void init() throws Exception {
        ImageCaptchaResourceManager imageCaptchaResourceManager = new DefaultImageCaptchaResourceManager();
        imageCaptchaGenerator = new MultiImageCaptchaGenerator(imageCaptchaResourceManager).init(true);
        imageCaptchaValidator = new BasicCaptchaTrackValidator();
        generatorField = ReflectUtil.getField(AbstractCaptcha.class, "generator");
    }

    @Mapping("image")
    public void image(@Param String sid, @Param(defaultValue = "-3") int type, @Param String style) throws Exception {
        String code = null;
        Context ctx = Context.current();
        switch (type) {
            case -4:// hutool
                if ("random".equals(style)) {
                    style = iStyles[RandomUtil.randomInt(0, iStyles.length)];
                }
                ICaptcha iCaptcha = iCaptcha(style, width, height);
                String generator = ctx.param("generator");
                if ("math".equals(generator) || ("random".equals(generator) && RandomUtil.randomBoolean())) {
                    ReflectUtil.setFieldValue(iCaptcha, generatorField, new MathGenerator());
                    code = "" + (int) Calculator.conversion(iCaptcha.getCode());
                } else {
                    code = iCaptcha.getCode();
                }
                iCaptcha.write(ctx.outputStream());
                break;
            default:// -3easy
                Captcha captcha = captcha(style);
                code = captcha.text();
                captcha.out(ctx.outputStream());
                break;
        }
        ctx.setRendered(true);
        log.debug("sid:{} code:{}", sid, code);
        cacheService.store(cachePrefix + sid, code, cacheSeconds);
    }

    private String[] iStyles = { "circle", "gif", "shear", "line" };

    private ICaptcha iCaptcha(String style, int width, int height) {
        switch (StrUtil.trimToEmpty(style)) {
            case "circle":
                return CaptchaUtil.createCircleCaptcha(width, height);
            case "gif":
                return CaptchaUtil.createGifCaptcha(width, height);
            case "shear":
                return CaptchaUtil.createShearCaptcha(width, height);
            default:
                return CaptchaUtil.createLineCaptcha(width, height);
        }
    }

    private String[] styles = { "gif", "chinese", "arithmetic", "chinesegif", "spec" };

    private Captcha captcha(String style) {
        switch (StrUtil.trimToEmpty(style)) {
            case "gif":
                return new GifCaptcha();
            case "chinese":
                return new ChineseCaptcha();
            case "arithmetic":
                return new ArithmeticCaptcha();
            case "chinesegif":
                return new ChineseGifCaptcha();
            case "random":
                return captcha(styles[RandomUtil.randomInt(0, styles.length)]);
            default:// spec
                return new SpecCaptcha();
        }
    }

    @Mapping("code")
    public ONode code(@Param(defaultValue = "-3") int type, @Param String style,
            @Param(defaultValue = "true") boolean secure) throws Exception {
        ONode n = new ONode();
        String sid = IdUtil.nanoId();
        Object code = null;
        switch (type) {
            case -4:// hutool
                if ("random".equals(style)) {
                    style = iStyles[RandomUtil.randomInt(0, iStyles.length)];
                }
                ICaptcha iCaptcha = iCaptcha(style, width, height);
                String generator = Context.current().param("generator");
                if ("math".equals(generator) || ("random".equals(generator) && RandomUtil.randomBoolean())) {
                    ReflectUtil.setFieldValue(iCaptcha, generatorField, new MathGenerator());
                    code = "" + (int) Calculator.conversion(iCaptcha.getCode());
                } else {
                    code = iCaptcha.getCode();
                }
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                iCaptcha.write(out);
                n.set("image", ImageUtil.encode(out.toByteArray(), null));
                break;
            case -5:// tianai
                ImageCaptchaInfo imageCaptchaInfo = imageCaptchaGenerator.generateCaptchaImage(style(style));
                Map<String, Object> map = imageCaptchaValidator.generateImageCaptchaValidData(imageCaptchaInfo);
                code = map.get("percentage");
                n.set("image", imageCaptchaInfo.getBackgroundImage());
                n.set("slider", imageCaptchaInfo.getSliderImage());
                break;
            default:// -3easy
                Captcha captcha = captcha(style);
                code = captcha.text();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                captcha.out(baos);
                n.set("image", ImageUtil.encode(baos.toByteArray(), null));
                break;
        }
        log.debug("sid:{} code:{}", sid, code);
        n.set("sid", sid);
        if (secure == false) {
            n.set("code", code);
        }
        cacheService.store(cachePrefix + sid, code, cacheSeconds);
        return n;
    }

    private String style(String style) {
        switch (StrUtil.trimToEmpty(style)) {
            case "ROTATE":
            case "CONCAT":
            case "WORD_IMAGE_CLICK":
                return style;
            case "RANDOM":
                int random = RandomUtil.randomInt(0, 3);
                return random == 0 ? "SLIDER" : (random == 1 ? "ROTATE" : "CONCAT");
            default:
                return "SLIDER";
        }
    }

    @Mapping("check")
    public ONode check(@Param @NotBlank String checkcode, @Param @NotBlank String sid) throws Exception {
        ONode n = new ONode();
        Object percentage = cacheService.get(cachePrefix + sid);
        boolean valid = false;
        if (percentage instanceof String) {
            String check = (String) percentage;
            if (checkcode.equalsIgnoreCase(check)) {
                valid = true;
            } else {
                String[] arr1 = check.split("[,;]");
                String[] arr2 = checkcode.split("[,;]");
                if (arr1.length == arr2.length && arr1.length == 8) {
                    for (int i = 0; i < arr1.length; i++) {
                        valid = imageCaptchaValidator.checkPercentage(Convert.toFloat(arr2[i]),
                                Convert.toFloat(arr1[i]));
                        if (valid == false) {
                            break;
                        }
                    }
                }
            }
        } else if (percentage instanceof Float) {
            Float oriPercentage = (Float) percentage;
            valid = imageCaptchaValidator.checkPercentage(Convert.toFloat(checkcode), oriPercentage);
        }
        log.debug("check:{}", percentage);
        n.set("valid", valid);
        return n;
    }
}
